// 
// Copyright (C) 2000-2003
// magustek co.
//

#ifndef MAGUS_MUTEX_HPP
#define MAGUS_MUTEX_HPP

#include <magus/config.hpp>
#include <magus/utility.hpp>
#include <magus/thread/detail/lock.hpp>
#include <magus/thread/detail/config.hpp>

#include <magus/thread/xtime.hpp>
//#include <magus/thread/thread.hpp>
#include <magus/thread/exceptions.hpp>
#include <stdexcept>
#include <cassert>

#if defined(MAGUS_HAS_WINTHREADS)
#   include <windows.h>
#   include <time.h>
#elif defined(MAGUS_HAS_PTHREADS)
#   include <pthread.h>
#   include <errno.h>
#endif

namespace magus {

struct xtime;

class MAGUS_THREAD_DECL mutex : private noncopyable
{
public:
    friend class detail::thread::lock_ops<mutex>;

    typedef detail::thread::scoped_lock<mutex> scoped_lock;

#if defined(MAGUS_HAS_WINTHREADS)
	mutex() : m_mutex(0)
	{
	    try
	    {
	        m_mutex = reinterpret_cast<void*>(new CRITICAL_SECTION);
	    }
	    catch (...)
	    {
	    }
	    if (!m_mutex)
	        throw thread_resource_error();
	    InitializeCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_mutex));
	}
	
	~mutex()
	{
	    DeleteCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_mutex));
	    delete reinterpret_cast<LPCRITICAL_SECTION>(m_mutex);
	}
	
private:
    typedef void* cv_state;
    void* m_mutex;

	void do_lock()
	{
	    EnterCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_mutex));
	}
	
	void do_unlock()
	{
	    LeaveCriticalSection(reinterpret_cast<LPCRITICAL_SECTION>(m_mutex));
	}
	
	void do_lock(cv_state&)
	{
	    do_lock();
	}
	
	void do_unlock(cv_state&)
	{
	    do_unlock();
	}

#elif defined(MAGUS_HAS_PTHREADS)

public:
	mutex()
	{
	    int res = 0;
	    res = pthread_mutex_init(&m_mutex, 0);
	    if (res != 0)
	        throw thread_resource_error();
	}
	
	~mutex()
	{
	    int res = 0;
	    res = pthread_mutex_destroy(&m_mutex);
	    assert(res == 0);
	}
	
private:
    struct cv_state
    {
        pthread_mutex_t* pmutex;
    };
    
    pthread_mutex_t m_mutex;
    
    void do_lock()
	{
	    int res = 0;
	    res = pthread_mutex_lock(&m_mutex);
	    if (res == EDEADLK) throw lock_error();
	    assert(res == 0);
	}
	
	void do_unlock()
	{
	    int res = 0;
	    res = pthread_mutex_unlock(&m_mutex);
	    if (res == EPERM) throw lock_error();
	    assert(res == 0);
	}
	
	void do_lock(cv_state&)
	{
	}
	
	void do_unlock(cv_state& state)
	{
	    state.pmutex = &m_mutex;
	}
#endif
};	// mutex

//////////////////////////////////////////////////////////////////////////////

class MAGUS_THREAD_DECL try_mutex : private noncopyable
{
public:
    friend class detail::thread::lock_ops<try_mutex>;

    typedef detail::thread::scoped_lock<try_mutex> scoped_lock;
    typedef detail::thread::scoped_try_lock<try_mutex> scoped_try_lock;

#if defined(MAGUS_HAS_WINTHREADS)
	// try_mutex
public:
	try_mutex()
	{
	    m_mutex = reinterpret_cast<void*>(CreateMutex(0, 0, 0));
	    if (!m_mutex)
	        throw thread_resource_error();
	}
	
	~try_mutex()
	{
	    int res = 0;
	    res = CloseHandle(reinterpret_cast<HANDLE>(m_mutex));
	    assert(res);
	}
	
private:
    typedef void* cv_state;
    void* m_mutex;

	void do_lock()
	{
	    int res = 0;
	    res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex), INFINITE);
	    assert(res == WAIT_OBJECT_0);
	}
	
	bool do_trylock()
	{
	    unsigned int res = 0;
	    res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex), 0);
	    assert(res != WAIT_FAILED && res != WAIT_ABANDONED);
	    return res == WAIT_OBJECT_0;
	}
	
	void do_unlock()
	{
	    int res = 0;
	    res = ReleaseMutex(reinterpret_cast<HANDLE>(m_mutex));
	    assert(res);
	}
	
	void do_lock(cv_state&)
	{
	    do_lock();
	}
	
	void do_unlock(cv_state&)
	{
	    do_unlock();
	}
	
#elif defined(MAGUS_HAS_PTHREADS)
	// try_mutex
public:
	try_mutex()
	{
	    int res = 0;
	    res = pthread_mutex_init(&m_mutex, 0);
	    if (res != 0)
	        throw thread_resource_error();
	}
	
	~try_mutex()
	{
	    int res = 0;
	    res = pthread_mutex_destroy(&m_mutex);
	    assert(res == 0);
	}
	
private:
    struct cv_state
    {
        pthread_mutex_t* pmutex;
    };
    pthread_mutex_t m_mutex;

	void do_lock()
	{
	    int res = 0;
	    res = pthread_mutex_lock(&m_mutex);
	    if (res == EDEADLK) throw lock_error();
	    assert(res == 0);
	}
	
	bool do_trylock()
	{
	    int res = 0;
	    res = pthread_mutex_trylock(&m_mutex);
	    if (res == EDEADLK) throw lock_error();
	    assert(res == 0 || res == EBUSY);
	    return res == 0;
	}
	
	void do_unlock()
	{
	    int res = 0;
	    res = pthread_mutex_unlock(&m_mutex);
	    if (res == EPERM) throw lock_error();
	    assert(res == 0);
	}
	
	void do_lock(cv_state&)
	{
	}
	
	void do_unlock(cv_state& state)
	{
	    state.pmutex = &m_mutex;
	}
#endif
};	// try_mutex

//////////////////////////////////////////////////////////////////////////////

class MAGUS_THREAD_DECL timed_mutex : private noncopyable
{
public:
    friend class detail::thread::lock_ops<timed_mutex>;

    typedef detail::thread::scoped_lock<timed_mutex> scoped_lock;
    typedef detail::thread::scoped_try_lock<timed_mutex> scoped_try_lock;
    typedef detail::thread::scoped_timed_lock<timed_mutex> scoped_timed_lock;


#if defined(MAGUS_HAS_WINTHREADS)
public:
	// timed_mutex
	timed_mutex()
	{
	    m_mutex = reinterpret_cast<void*>(CreateMutex(0, 0, 0));
	    if (!m_mutex)
	        throw thread_resource_error();
	}
	
	~timed_mutex()
	{
	    int res = 0;
	    res = CloseHandle(reinterpret_cast<HANDLE>(m_mutex));
	    assert(res);
	}
	
private:
    typedef void* cv_state;
    void* m_mutex;

	void do_lock()
	{
	    int res = 0;
	    res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex), INFINITE);
	    assert(res == WAIT_OBJECT_0);
	}
	
	bool do_trylock()
	{
	    unsigned int res = 0;
	    res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex), 0);
	    assert(res != WAIT_FAILED && res != WAIT_ABANDONED);
	    return res == WAIT_OBJECT_0;
	}
	
	bool do_timedlock(const xtime& xt)
	{
	    unsigned int res = 0;
	    for (;;)
	    {
	        int milliseconds;
	        to_duration(xt, milliseconds);
	
	        res = WaitForSingleObject(reinterpret_cast<HANDLE>(m_mutex),
	            milliseconds);
	        assert(res != WAIT_FAILED && res != WAIT_ABANDONED);
	
	        if (res == WAIT_TIMEOUT)
	        {
	            xtime cur;
	            xtime_get(&cur, TIME_UTC);
	            if (xtime_cmp(xt, cur) > 0)
	                continue;
	        }
	
	        return res == WAIT_OBJECT_0;
	    }
	}
	
	void do_unlock()
	{
	    int res = 0;
	    res = ReleaseMutex(reinterpret_cast<HANDLE>(m_mutex));
	    assert(res);
	}
	
	void do_lock(cv_state&)
	{
	    do_lock();
	}
	
	void do_unlock(cv_state&)
	{
	    do_unlock();
	}
	
#elif defined(MAGUS_HAS_PTHREADS)
public:
	// timed_mutex
	timed_mutex()
	    : m_locked(false)
	{
	    int res = 0;
	    res = pthread_mutex_init(&m_mutex, 0);
	    if (res != 0)
	        throw thread_resource_error();
	
	    res = pthread_cond_init(&m_condition, 0);
	    if (res != 0)
	    {
	        pthread_mutex_destroy(&m_mutex);
	        throw thread_resource_error();
	    }
	}
	
	~timed_mutex()
	{
	    assert(!m_locked);
	    int res = 0;
	    res = pthread_mutex_destroy(&m_mutex);
	    assert(res == 0);
	
	    res = pthread_cond_destroy(&m_condition);
	    assert(res == 0);
	}

private:
    struct cv_state
    {
        pthread_mutex_t* pmutex;
    };
    
    pthread_mutex_t m_mutex;
    pthread_cond_t m_condition;
    bool m_locked;
	
	void do_lock()
	{
	    int res = 0;
	    res = pthread_mutex_lock(&m_mutex);
	    assert(res == 0);
	
	    while (m_locked)
	    {
	        res = pthread_cond_wait(&m_condition, &m_mutex);
	        assert(res == 0);
	    }
	
	    assert(!m_locked);
	    m_locked = true;
	
	    res = pthread_mutex_unlock(&m_mutex);
	    assert(res == 0);
	}
	
	bool do_trylock()
	{
	    int res = 0;
	    res = pthread_mutex_lock(&m_mutex);
	    assert(res == 0);
	
	    bool ret = false;
	    if (!m_locked)
	    {
	        m_locked = true;
	        ret = true;
	    }
	
	    res = pthread_mutex_unlock(&m_mutex);
	    assert(res == 0);
	    return ret;
	}
	
	bool do_timedlock(const xtime& xt)
	{
	    int res = 0;
	    res = pthread_mutex_lock(&m_mutex);
	    assert(res == 0);
	
	    timespec ts;
	    to_timespec(xt, ts);
	
	    while (m_locked)
	    {
	        res = pthread_cond_timedwait(&m_condition, &m_mutex, &ts);
	        assert(res == 0 || res == ETIMEDOUT);
	
	        if (res == ETIMEDOUT)
	            break;
	    }
	
	    bool ret = false;
	    if (!m_locked)
	    {
	        m_locked = true;
	        ret = true;
	    }
	
	    res = pthread_mutex_unlock(&m_mutex);
	    assert(res == 0);
	    return ret;
	}
	
	void do_unlock()
	{
	    int res = 0;
	    res = pthread_mutex_lock(&m_mutex);
	    assert(res == 0);
	
	    assert(m_locked);
	    m_locked = false;
	
	    res = pthread_cond_signal(&m_condition);
	    assert(res == 0);
	
	    res = pthread_mutex_unlock(&m_mutex);
	    assert(res == 0);
	}
	
	void do_lock(cv_state&)
	{
	    int res = 0;
	    while (m_locked)
	    {
	        res = pthread_cond_wait(&m_condition, &m_mutex);
	        assert(res == 0);
	    }
	
	    assert(!m_locked);
	    m_locked = true;
	
	    res = pthread_mutex_unlock(&m_mutex);
	    assert(res == 0);
	}
	
	void do_unlock(cv_state& state)
	{
	    int res = 0;
	    res = pthread_mutex_lock(&m_mutex);
	    assert(res == 0);
	
	    assert(m_locked);
	    m_locked = false;
	
	    res = pthread_cond_signal(&m_condition);
	    assert(res == 0);
	
	    state.pmutex = &m_mutex;
	}
#endif
};	// timed_mutex

} // namespace magus

// Change Log:
//    16 JUN 03  Initial version.

#endif // MAGUS_MUTEX_HPP
